import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { User } from './user.model';
import type { CreateUserDto as CreateDto } from './dtos/create.dto';
import type { Includeable, Order, WhereOptions } from 'sequelize';
import { RuntimeException } from 'src/exceptions/runtime.exception';
import { UpdateUserDto as UpdateDto } from './dtos/update.dto';
import { UserRole } from './user-role.model';
import { Sequelize } from 'sequelize-typescript';
import { genSalt, hash } from 'bcrypt';
import { RoleService } from 'src/role/role.service';
import { EXCEPTION } from 'src/constants';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User) private userModel: typeof User,
    @InjectModel(UserRole) private userRoleModel: typeof UserRole,
    private readonly sequelize: Sequelize,
    private roleService: RoleService,
  ) {}

  async hashedPassword(password: string) {
    const salt = await genSalt(10);
    return await hash(password, salt);
  }

  async findAll(
    limit: number,
    page: number,
    where: WhereOptions,
    order: Order,
  ): Promise<{ total: number; data: CreateDto[] }> {
    const total = await this.userModel.count({ where });
    const users = await this.userModel.findAll({
      attributes: { exclude: ['password'] },
      offset: (page - 1) * limit,
      where,
      limit,
      // order: [["created_at", sort]],
      order,
      // 返回原始对象，而不是模型对象，节约性能
      raw: true,
    });
    // 日期服务端就不处理，以 UTC 返回，前端显示的时候再转
    // users.forEach((user) => {
    //   user.created_at = dayjs(user.created_at).format("YYYY-MM-DD HH:mm:ss");
    //   user.updated_at = dayjs(user.updated_at).format("YYYY-MM-DD HH:mm:ss");
    // });
    return {
      total,
      data: users.map((user) => ({
        ...user,
        roles: user.roles.map((role) => role.code),
      })),
    };
  }

  async findByPk(id: string): Promise<User> {
    const user = await this.userModel.findByPk(id);
    if (!user) {
      throw new RuntimeException('用户不存在', 1);
    }
    return user;
  }

  async findOne(where: WhereOptions): Promise<User> {
    return this.userModel.findOne({
      where,
    });
  }

  async remove(id: string): Promise<boolean> {
    const user = await this.findByPk(id);

    // 开启事务
    const t = await this.sequelize.transaction();
    const transactionHost = { transaction: t };
    try {
      await user.destroy(transactionHost);

      await this.userRoleModel.destroy({
        where: {
          user_id: user.id,
        },
        ...transactionHost,
      });

      return true;
    } catch (err) {
      await t.rollback();
      throw err;
    }
  }

  async removeBatch(ids: string[]): Promise<boolean> {
    // 开启事务
    const t = await this.sequelize.transaction();
    const transactionHost = { transaction: t };
    try {
      await this.userModel.destroy({
        where: {
          id: ids,
        },
        ...transactionHost,
      });

      await this.userRoleModel.destroy({
        where: {
          user_id: ids,
        },
        ...transactionHost,
      });

      await t.commit();

      return true;
    } catch (err) {
      await t.rollback();
      throw err;
    }
  }

  async createBatch(batchData: string) {
    const data = JSON.parse(batchData);
    if (!data) {
      throw new RuntimeException('数据错误', 1);
    }

    const users = await Promise.all(
      data
        .filter((user) => user.username)
        .map(
          /*多个 promise 对象*/ async (user) => {
            const findUser = await User.findOne({
              where: { username: user.username },
            });
            if (findUser) {
              throw new RuntimeException(`用户名：${user.username} 已存在！`);
            }
            return {
              username: `${user.username || ''}`,
              mobile: `${user.mobile || ''}`,
              password: await this.hashedPassword(
                `${user.password || '123456'}`,
              ),
              email: `${user.email || ''}`,
              real_name: `${user.real_name || ''}`,
              id_card: `${user.id_card || ''}`,
              sex: `${user.sex === '男' ? 1 : 2}`,
            };
          },
        ),
    );

    await User.bulkCreate(users, {
      fields: [
        'username',
        'mobile',
        'password',
        'email',
        'real_name',
        'id_card',
        'sex',
      ],
    });

    return true;
  }

  async create({ roles, ...data }: CreateDto) {
    const username = data.username;
    const user = await this.findOne({ username });
    if (user !== null) {
      throw new RuntimeException('用户已存在', 1);
    }

    data.password = await this.hashedPassword(data.password);

    // 开启事务
    const t = await this.sequelize.transaction();
    const transactionHost = { transaction: t };
    try {
      const user = await this.userModel.create(data, transactionHost);

      if (roles) {
        for (const id of roles) {
          const role = await this.roleService.findByPk(id);
          if (role) {
            const rel = await this.userRoleModel.findOne({
              where: { user_id: user.id, role_id: role.id },
            });
            if (!rel) {
              await this.userRoleModel.create(
                { user_id: user.id, role_id: role.id },
                transactionHost,
              );
            }
          }
        }
      }

      await t.commit();

      return user;
    } catch (err) {
      await t.rollback();
      throw err;
    }
  }

  async update(id: string, { roles, ...data }: UpdateDto) {
    const user = await this.findByPk(id);
    // 不允许修改的字段
    const notAllow = [
      'id',
      'username',
      'created_at',
      'updated_at',
      'deleted_at',
    ].find((key) => data[key]);
    if (notAllow) {
      throw new RuntimeException(`不允许修改${notAllow}`, 2);
    }

    if ('password' in data) {
      data.password = await this.hashedPassword(data.password);
    }
    // 开启事务
    const t = await this.sequelize.transaction();
    const transactionHost = { transaction: t };
    try {
      user.set(data);

      const saved = await user.save(transactionHost);

      if (roles) {
        roles = roles.map((r) => `${r}`);
        const _roles = (
          await this.userRoleModel.findAll({
            where: {
              user_id: user.id,
            },
            raw: true,
          })
        ).map((r) => `${r.role_id}`);
        // new: roles, old: _roles
        // new - old = 新增项
        // old - new = 删除项
        const adds = roles.filter((id) => !_roles.includes(id));
        const dels = _roles.filter((_id) => !roles.includes(_id));
        if (adds.length) {
          await this.userRoleModel.bulkCreate(
            adds.map((role_id) => ({ user_id: user.id, role_id })),
            transactionHost,
          );
        }
        if (dels.length) {
          await this.userRoleModel.destroy({
            where: {
              user_id: user.id,
              role_id: dels,
            },
            force: true,
            ...transactionHost,
          });
        }
      }

      await t.commit();

      return saved;
    } catch (err) {
      await t.rollback();
      throw err;
    }
  }

  async findOneWith(
    where: WhereOptions,
    ...include: Includeable[]
  ): Promise<User> {
    return this.userModel.findOne({
      where,
      include,
    });
  }

  async getProfile(userPayload: UserPayload) {
    const userId = userPayload?.sub;
    if (!userId) {
      throw new RuntimeException(
        '登录状态异常，请重新登录',
        EXCEPTION.NO_LOGIN,
      );
    }
    const user = await this.findByPk(userId);
    return { ...user.toJSON(), root: !!userPayload.root };
  }
}
